# [WIP] 当前未完工，这个艰巨的任务就交给师弟了

import os
import inspect
from inspect import Parameter, signature
from typing import Callable, Dict, Type

from tornado.web import RequestHandler


stub_code_template = """import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.stereotype.Component;
{importer}

/**
 * 客户端请求接口, 微服务[{app_name}]的自动生成的桩代码(generate_java_stub命令)
 * @author 陈正阳
 * 
 * 此接口由pmm工具自动生成。请保证在pom.xml中有下述包: 
<dependency>
	<groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-web</artifactId>
</dependency>
		
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
	
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
 */

@Component
@FeignClient(value = "CLIENT-{uuid}")
public interface {app_name}Service{
{method_code}
}
"""

stub_method_code_template = """
\t@{method}Mapping("{url}")
\tpublic {ret_type} {func_name}({args});
"""

stub_method_doc_template = "/** {} */"


def to_camel(x: str) -> str:
    """下划线式转为驼峰式字符串

    Args:
        x (str): 下划线式字符串

    Returns:
        str: 驼峰式字符串
    """
    seg_list = x.split("_")
    for i, seg in enumerate(seg_list):
        seg_list[i] = seg.capitalize()
    return "".join(seg_list)


def get_better_formatannotation(annotation_dict: dict) -> Callable:
    """拿到一个更好的标注格式化方法，注入到inspect中

    Args:
        annotation_dict (dict): 标注字典, {包名: {名称: 本地名}}

    Raises:
        TypeError: 参数类型标注错误

    Returns:
        Callable: 标注格式化方法
    """

    # 维护一个名称计数 {名称: 计数}
    name_counter = {}

    def set_counter(name: str) -> str:
        if name_counter.get(name) is None:
            local_name = name
            name_counter[local_name] = 1
        else:
            cnt = name_counter[name] + 1
            local_name = name + str(cnt)
            name_counter[name] = cnt
        return local_name

    def get_local_name(pkg_name: str, name: str) -> str:
        if annotation_dict.get(pkg_name) is None:
            local_name = set_counter(name)
            annotation_dict[pkg_name] = {name: local_name}
        else:
            pkg: dict = annotation_dict[pkg_name]
            if pkg.get(name) is None:
                local_name = set_counter(name)
                pkg[name] = local_name
            else:
                local_name = pkg[name]

        return local_name

    def _better_formatannotation(annotation, base_module=None):
        if getattr(annotation, '__module__', None) == 'typing':
            args: list = getattr(annotation, '__args__', None)
            if args is None:
                raise TypeError(
                    "请不要仅仅使用%s,需要加上类型参数,如List[int]" % str(annotation))

            pkg_name = annotation.__module__
            name = annotation._name
            local_name = get_local_name(pkg_name, name)
            args_strs = []
            for arg in args:
                args_strs.append(_better_formatannotation(arg))
            ret = local_name + "[" + ", ".join(args_strs) + "]"

            return ret

        if isinstance(annotation, type):
            pkg_name = annotation.__module__
            if pkg_name in ('builtins', base_module):
                return annotation.__qualname__

            name = annotation.__qualname__
            local_name = get_local_name(pkg_name, name)
            return local_name

        return repr(annotation)

    return _better_formatannotation


def generate_stub(app_name: str, python_service_list: Dict[str, Type[RequestHandler]]) -> str:
    methods_code = []

    # Warning: 侵入inspect包替换formatannotation方法, 这是危险操作，一般不建议使用
    annotation_dict: dict = {}
    inspect.formatannotation = get_better_formatannotation(annotation_dict)

    for url, service in python_service_list.items():
        sig = signature(service.service_handler)
        method = service.accepted_methods[0]

        # 方法签名
        sig_text = service.service_handler.__name__ + str(sig)

        # 开头引入包
        importer = ""
        for pkg_name, mods in annotation_dict.items():
            pkg_importer = "from %s import " % pkg_name

            mods_importer = []
            for name, local_name in mods.items():
                if name == local_name:
                    mods_importer.append(name)
                else:
                    mods_importer.append("%s as %s" % (name, local_name))
            pkg_importer += ", ".join(mods_importer)
            importer += pkg_importer+"\n"

        # 方法注释
        doc = service.service_handler.__doc__
        if doc is not None:
            doc = stub_method_doc_template.format(doc)
        else:
            doc = ""

        # 包装数据
        positional = []
        args = "[]"
        kwargs = "{}"

        for param_name, param in sig.parameters.items():
            # 包装数据
            if param.kind == Parameter.VAR_POSITIONAL:
                args = param_name
            elif param.kind == Parameter.VAR_KEYWORD:
                kwargs = param_name
            else:
                pass
                # positional.append(
                #     stub_method_wrapped_positional_template.format(param_name))
        positional = ",\n".join(positional)

        # python_only?
        py_only = service.python_only

        # 方法全部代码
        method_code = stub_method_code_template.format(
            sig=sig_text,
            doc=doc,
            args_=args,
            kwargs_=kwargs,
            positional=positional,
            app_name=app_name,
            url=url,
            method=method,
            py_only=py_only
        )

        methods_code.append(method_code)

    methods_code = "".join(methods_code)

    stub_code = stub_code_template.format(
        importer=importer, app_name=app_name, methods_code=methods_code)

    # 后处理
    stub_code = stub_code.replace(
        "    _wrapped_positional.update({})\n", "")
    stub_code = stub_code.replace("{\n\n    }", "{}")
    stub_code = stub_code.replace("\n    \n", "\n\n")

    stub_code = stub_code[:-1]

    return stub_code


def generate_java_stub_to_file(stub_dir: str, app_name: str, python_service_list: Dict[str, Type[RequestHandler]]):
    app_name = to_camel(app_name)
    stub_code = generate_stub(app_name, python_service_list)

    stub_filepath = os.path.join(stub_dir, "%sService.java" % app_name)
    stub_dir = os.path.abspath(stub_dir)
    if not os.path.exists(stub_dir):
        os.makedirs(stub_dir, exist_ok=True)

    with open(stub_filepath, "w", encoding="utf-8") as fp:
        fp.write(stub_code)
